\chapter{面向对象}

\section{封装}

\subsection{类与对象}

在面向对象编程中，把构成问题的事物分解成各个对象，每个对象都有自己的数据和行为，程序通过对象之间的交互来实现功能。\\

类（class）是一个模板，定义了对象的属性和方法，用来描述同一类对象的共同特征和行为。对象（object）是类的实例，它具有类定义的属性和方法。\\

实例化一个类对象之后就可以通过访问对象的属性和方法来操作对象。\\

\mybox{银行账户}

\begin{lstlisting}[language=Python]
class BankAccount:
    def deposit(self, amount):
        self.balance += amount
    
    def withdraw(self, amount):
        self.balance -= amount

def main():
    account = BankAccount()
    account.owner = "Terry"
    account.account = "6250941006528599"
    account.balance = 50

    print("Owner:", account.owner)
    print("Account:", account.account)
    print("Balance:", account.balance)

    account.deposit(100)
    print("Balance:", account.balance)

    account.withdraw(70)
    print("Balance:", account.balance)

if __name__ == "__main__":
    main()
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Owner: Terry
Account: 6250941006528599
Balance: 50
Balance: 150
Balance: 80
\end{verbatim}
\end{tcolorbox}

\vspace{0.5cm}

\subsection{封装（Encapsulation）}

封装是面向对象的重要原则，尽可能隐藏对象的内部实现细节。封装可以认为是一个保护屏障，防止该类的数据被外部随意访问。当要访问该类的数据时，必须通过指定的接口。合适的封装可以让代码更容易理解和维护，也加强了程序的安全性。\\

为了实现封装，需要对类的属性和方法进行访问权限的控制。通常会将类的属性设置为私有属性，然后对外提供一对setter/getter方法来访问该属性。\\

为了避免方法的参数与类的属性重名造成歧义，可以使用self关键字用来指代当前对象。\\

\mybox{银行账户}

\begin{lstlisting}[language=Python]
class BankAccount:
    __ACCOUNT_DIGITS = 16
    __owner = ""
    __account = ""
    __balance = 0

    def set_owner(self, owner):
        if owner != "":
            self.__owner = owner
    
    def get_owner(self):
        return self.__owner
    
    def set_account(self, account):
        if len(account) == self.__ACCOUNT_DIGITS:
            self.__account = account
    
    def get_account(self):
        return self.__account

    def set_balance(self, balance):
        if balance >= 0:
            self.__balance = balance
    
    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        if amount <= 0:
            return False
        self.__balance += amount
        return True

    def withdraw(self, amount):
        if amount <= 0 or amount > self.__balance:
            return False
        self.__balance -= amount
        return True

def main():
    account = BankAccount()
    account.set_owner("Terry")
    account.set_account("6250941006528599")
    account.set_balance(50)

    print("Owner:", account.get_owner())
    print("Account:", account.get_account())
    print("Balance:", account.get_balance())

    account.deposit(100)
    print("Balance:", account.get_balance())

    account.withdraw(70)
    print("Balance:", account.get_balance())

if __name__ == "__main__":
    main()
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Owner: Terry
Account: 6250941006528599
Balance: 50
Balance: 150
Balance: 80
\end{verbatim}
\end{tcolorbox}

\vspace{0.5cm}

\subsection{构造方法（Constructor）}

构造方法是一种特殊的方法，会在创建对象时自动调用，用于创建并初始化对象，构造方法的名字为\_\_init\_\_()。\\

\mybox{银行账户}

\begin{lstlisting}[language=Python]
class BankAccount:
    def __init__(self, owner, account, balance):
        self.__ACCOUNT_DIGITS = 16
        
        if owner != "":
            self.__owner = owner
        
        if len(account) == self.__ACCOUNT_DIGITS:
            self.__account = account
        
        if balance >= 0:
            self.__balance = balance
    
    def set_owner(self, owner):
        if owner != "":
            self.__owner = owner
    
    def get_owner(self):
        return self.__owner

    def set_account(self, account):
        if len(account) == self.__ACCOUNT_DIGITS:
            self.__account = account

    def get_account(self):
        return self.__account

    def set_balance(self, balance):
        if balance >= 0:
            self.__balance = balance

    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        if amount <= 0:
            return False
        
        self.__balance += amount
        return True

    def withdraw(self, amount, fee=0):
        if amount <= 0 or amount + fee > self.__balance:
            return False
        
        self.__balance -= amount + fee
        return True

def main():
    account = BankAccount("Terry", "6250941006528599", 50)

    print("Account Balance:", account.get_balance())

    account.withdraw(20)
    print("Account Balance:", account.get_balance())

    account.withdraw(10, 1)
    print("Account Balance:", account.get_balance())

if __name__ == "__main__":
    main()
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Account Balance: 50
Account Balance: 30
Account Balance: 19
\end{verbatim}
\end{tcolorbox}

\newpage

\section{继承}

\subsection{继承（Inheritance）}

继承指一个类可以继承另一个类的特征和行为，并可以对其进行扩展。这样就可以避免在多个类中重复定义相同的特征和行为。\\

\begin{figure}[H]
    \centering
    \begin{tikzpicture}
        \begin{class}[text width = 5cm]{Product}{0,0}
            \attribute{- name : str}
            \attribute{- price : float}
            \operation{+ Product(str, float)}
            \operation{+ get\_name() : String}
            \operation{+ get\_nrice() : float}
            \operation{+ set\_name(str) : None}
            \operation{+ set\_price(float) : None}
        \end{class}

        \begin{class}[text width = 6cm]{Food}{-4,-7}
            \inherit{Product}
            \attribute{- calories : int}
            \operation{+ Food(str, float, int)}
            \operation{+ get\_calories() : int}
            \operation{+ set\_calories(int) : None}
        \end{class}

        \begin{class}[text width = 6cm]{Drink}{4,-7}
            \inherit{Product}
            \attribute{- size : str}
            \operation{+ Drink(str, float, str)}
            \operation{+ get\_size() : str}
            \operation{+ set\_size(str) : None}
        \end{class}
    \end{tikzpicture}
    \caption{继承}
\end{figure}

产生继承关系后，子类可以通过super关键字调用父类中的属性和方法，也可以定义子类独有的属性和方法。\\

在创建子类对象时，会先调用父类的构造方法，然后再调用子类的构造方法。因此父类中必须存在一个构造方法，否则将无法创建子类对象。\\

\mybox{麦当劳}

\begin{lstlisting}[language=Python]
class Product:
    def __init__(self, name, price):
        self.__name = name
        self.__price = price
    
    def get_name(self):
        return self.__name
    
    def set_name(self, name):
        self.__name = name

    def get_price(self):
        return self.__price

    def set_price(self, price):
        self.__price = price
\end{lstlisting}

\begin{lstlisting}[language=Python]
from product import Product

class Food(Product):
    def __init__(self, name, price, calories):
        super().__init__(name, price)
        self.__calories = calories
    
    def get_calories(self):
        return self.__calories
    
    def set_calories(self, calories):
        self.__calories = calories
\end{lstlisting}

\begin{lstlisting}[language=Java]
from product import Product

class Drink(Product):
    def __init__(self, name, price, size):
        super().__init__(name, price)
        self.__size = size

    def get_size(self):
        return self.__size

    def set_size(self, size):
        self.__size = size
\end{lstlisting}

\begin{lstlisting}[language=Java]
from food import Food
from drink import Drink

def main():
    food = Food("Cheeseburger", 5.45, 302)
    drink = Drink("Coke", 3.7, "Large")

    print("Food: %s ($%.2f) %d Kcal" % (
        food.get_name(), food.get_price(), food.get_calories())
    )
    print("Drink: %s ($%.2f) %s" % (
        drink.get_name(), drink.get_price(), drink.get_size())
    )

if __name__ == "__main__":
    main()
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Food: Cheeseburger ($5.45) 302 Kcal
Drink: Coke ($3.70) Large
	\end{verbatim}
\end{tcolorbox}

\vspace{0.5cm}

\subsection{重写（Override）}

object类是所有类的根类，所有的类都直接或者间接地继承自object类。object类中包含的方法在其它所有类中都可以使用，例如\_\_str\_\_()方法等。\\

当直接输出一个对象时，会自动调用该对象的\_\_str\_\_()方法，将其以字符串的形式输出。

\vspace{-0.5cm}

\begin{lstlisting}[language=Java]
print(food);   # <food.Food object at 0x0000011CE7BC3E10>
\end{lstlisting}

在没有重写\_\_str\_\_()方法的情况下，输出的内容是对象的类名及其地址，但这并不是预期想要的结果。因此，可以重写从父类继承的\_\_str\_\_()，以满足程序的需求。\\

\mybox{麦当劳}

\begin{lstlisting}[language=Python]
class Product:
    def __init__(self, name, price):
        self.__name = name
        self.__price = price
    
    def get_name(self):
        return self.__name
    
    def set_name(self, name):
        self.__name = name

    def get_price(self):
        return self.__price

    def set_price(self, price):
        self.__price = price
    
    def __str__(self):
        return "%s ($%.2f)" % (self.__name, self.__price)
\end{lstlisting}

\begin{lstlisting}[language=Python]
from product import Product

class Food(Product):
    def __init__(self, name, price, calories):
        super().__init__(name, price)
        self.__calories = calories
    
    def get_calories(self):
        return self.__calories
    
    def set_calories(self, calories):
        self.__calories = calories
    
    def __str__(self):
        return "Food: %s %d Kcal" % (
            super().__str__(), self.__calories
        )
\end{lstlisting}

\begin{lstlisting}[language=Python]
from product import Product

class Drink(Product):
    def __init__(self, name, price, size):
        super().__init__(name, price)
        self.__size = size

    def get_size(self):
        return self.__size

    def set_size(self, size):
        self.__size = size
    
    def __str__(self):
        return "Drink: %s %s" % (super().__str__(), self.__size)
\end{lstlisting}

\begin{lstlisting}[language=Python]
from food import Food
from drink import Drink

def main():
    food = Food("Cheeseburger", 5.45, 302)
    drink = Drink("Coke", 3.7, "Large")

    print(food)
    print(drink)

if __name__ == "__main__":
    main()
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Food: Cheeseburger ($5.45) 302 Kcal
Drink: Coke ($3.70) Large
	\end{verbatim}
\end{tcolorbox}

\newpage

\section{多态}

\subsection{多态（Polymorphism）}

多态是指对象可以具有多种形态，即同一个对象在不同时刻表现出不同的行为。例如Dog和Cat都是Animal的子类，因此可以将子类对象赋值给父类引用，从而产生多种形态。\\

由子类类型转型为父类类型，称为向上转型。由父类类型转型为子类类型，称为向下转型。\\

\mybox{员工工资}

\begin{lstlisting}[language=Python]
class Employee:
    def __init__(self, name):
        self.__name = name
    
    def get_name(self):
        return self.__name
    
    def set_name(self, name):
        self.__name = name
    
    def get_salary(self):
        pass

class FullTimeEmployee(Employee):
    def __init__(self, name, basic_salary, bonus):
        super().__init__(name)
        self.__basic_salary = basic_salary
        self.__bonus = bonus
    
    def get_salary(self):
        return self.__basic_salary + self.__bonus
    
class PartTimeEmployee(Employee):
    def __init__(self, name, daily_wage, working_days):
        super().__init__(name)
        self.__daily_wage = daily_wage
        self.__working_days = working_days

    def get_salary(self):
        return self.__daily_wage * self.__working_days
\end{lstlisting}

\begin{lstlisting}[language=Python]
from employee import FullTimeEmployee
from employee import PartTimeEmployee

def main():
    employees = [
        FullTimeEmployee('Alice', 5783, 173),
        PartTimeEmployee('Bob', 150, 15)
    ]

    for employee in employees:
        print(
            employee.get_name() + ': $' + str(employee.get_salary())
        )

if __name__ == '__main__':
    main()
\end{lstlisting}

\begin{tcolorbox}
    \mybox{运行结果}
    \begin{verbatim}
Alice: $5956
Bob: $2250
	\end{verbatim}
\end{tcolorbox}

\newpage